﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace IssueIT.Core.Util
{
	public interface IObjectConverter
	{
		bool CanBeParsed(Type type);
		object FromString(string stringValue, Type type);
		T FromString<T>(string stringValue);

		void RegisterConverter<T>(Func<string, T> converter);
		void RegisterConverterFamily(IObjectConverterFamily family);
	}

	public class ObjectConverter : IObjectConverter
	{
		private static readonly IObjectConverterFamily[] DefaultConverterFamilies = new IObjectConverterFamily[]
		{
			new ArrayConverterFamily(),
			new EnumConverterFamily(),
			new NullableConverterFamily(),
			new TypeDescriptorFamily(),
		};

		private readonly IList<IObjectConverterFamily> _families;
		private readonly Cache<Type, Func<string, object>> _converters;

		public const string NullString = "NULL";
		public const string EmptyString = "EMPTY";
		public const string BlankString = "BLANK";

		public ObjectConverter()
		{
			_families = new List<IObjectConverterFamily>(DefaultConverterFamilies);
			_converters = new Cache<Type, Func<string, object>>(findConverter);
			_converters[typeof(string)] = parseString;
		}

		private Func<string, object> findConverter(Type type)
		{
			IObjectConverterFamily familyMatch = _families.FirstOrDefault(family => family.Matches(type, this));
			if( familyMatch!=null )
			{
				return familyMatch.CreateConverter(type, _converters);
			}
			throw new InvalidOperationException(string.Format("Unable to find an IObjectConverterFamily implementation for {0}", type.FullName));
		}

		private static string parseString(string s)
		{
			if( s==BlankString || s==EmptyString )
			{
				return string.Empty;
			}

			return s==NullString ? null : s;
		}

		public bool CanBeParsed(Type type)
		{
			return _converters.Contains(type) || _families.Any(family => family.Matches(type, this));
		}

		public virtual object FromString(string stringValue, Type type)
		{
			return stringValue==NullString ? null : _converters[type](stringValue);
		}

		public virtual T FromString<T>(string stringValue)
		{
			return (T) FromString(stringValue, typeof(T));
		}

		public void RegisterConverter<T>(Func<string, T> converter)
		{
			_converters[typeof(T)] = s => converter(s);
		}

		public void RegisterConverterFamily(IObjectConverterFamily family)
		{
			_families.Insert(0, family);
		}
	}
}